/*
 *    Copyright (c) 2018-2025, cloud All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * Neither the name of the pig4cloud.com developer nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * Author: cloud
 */
package com.fuyao.cloud.admin.service.impl;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.GetObjectRequest;
import com.amazonaws.services.s3.model.S3Object;
import com.amazonaws.services.s3.model.S3ObjectInputStream;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fuyao.cloud.admin.api.entity.SysFile;
import com.fuyao.cloud.admin.mapper.SysFileMapper;
import com.fuyao.cloud.admin.service.SysFileService;
import com.fuyao.cloud.common.core.util.R;
import com.fuyao.cloud.common.file.core.FileProperties;
import com.fuyao.cloud.common.file.core.FileTemplate;
import com.fuyao.cloud.common.security.util.SecurityUtils;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRange;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 文件管理
 *
 * @author Luckly
 * @date 2019-06-18 17:18:42
 */
@Slf4j
@Service
@AllArgsConstructor
public class SysFileServiceImpl extends ServiceImpl<SysFileMapper, SysFile> implements SysFileService {

    private final FileTemplate fileTemplate;

    private final FileProperties properties;

    private final AmazonS3 amazonS3;

    /**
     * 上传文件
     *
     * @param file
     * @return
     */
    @Override
    public R uploadFile(MultipartFile file) {
        String fileName = IdUtil.simpleUUID() + StrUtil.DOT + FileUtil.extName(file.getOriginalFilename());
        Map<String, String> resultMap = new HashMap<>(4);
        resultMap.put("bucketName", properties.getBucketName());
        resultMap.put("fileName", fileName);
        resultMap.put("url", String.format("/admin/sys-file/%s/%s", properties.getBucketName(), fileName));

        try (InputStream inputStream = file.getInputStream()) {
            fileTemplate.putObject(properties.getBucketName(), fileName, inputStream, file.getContentType());
            // 文件管理数据记录,收集管理追踪文件
            fileLog(file, fileName);
        } catch (Exception e) {
            log.error("上传失败", e);
            return R.failed(e.getLocalizedMessage());
        }
        return R.ok(resultMap);
    }

    /**
     * 读取文件
     *
     * @param bucket
     * @param fileName
     * @param request
     * @param response
     */
    @Override
    public void getFile(String bucket, String fileName, HttpServletRequest request, HttpServletResponse response) {
        String range = request.getHeader(HttpHeaders.RANGE);
        if (StringUtils.hasLength(range)) {
            downloadPart(bucket, fileName, request, response, range);
        } else {
            downloadAll(bucket, fileName, request, response);
        }
    }

    private void downloadAll(String bucket, String fileName, HttpServletRequest request, HttpServletResponse response) {
        S3ObjectInputStream s3ObjectInputStream = null;
        GetObjectRequest getObjectRequest = new GetObjectRequest(bucket, fileName.replace(".mp4", ""));
        S3Object s3Object = amazonS3.getObject(getObjectRequest);
        ServletOutputStream outputStream;
        try {
            outputStream = response.getOutputStream();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        try {
            response.setHeader(HttpHeaders.CACHE_CONTROL, "max-age=14400");
            // response.setContentType("application/octet-stream; charset=UTF-8");
            response.setContentType(s3Object.getObjectMetadata().getContentType());
            s3ObjectInputStream = s3Object.getObjectContent();

            response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + URLEncoder.encode(fileName, "UTF8").replaceAll("\\+", "%20"));
            response.setContentLengthLong(s3Object.getObjectMetadata().getContentLength());
            IOUtils.copy(s3ObjectInputStream, outputStream);
            outputStream.close();
            outputStream.flush();
        } catch (Exception e) {
            if (s3ObjectInputStream != null) {
                s3ObjectInputStream.abort();
            }
            IOUtils.closeQuietly(s3Object);
            IOUtils.closeQuietly(outputStream);
            log.warn(e.getMessage());
        }
    }

    private void downloadPart(String bucket, String fileName, HttpServletRequest request, HttpServletResponse response, String range) {
        S3ObjectInputStream s3ObjectInputStream = null;
        ServletOutputStream outputStream;
        try {
            outputStream = response.getOutputStream();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        GetObjectRequest getObjectRequest = new GetObjectRequest(bucket, fileName);
        long start = 0;
        long end = 0;
        int rangeType;
        HttpRange httpRange;
        Pattern compile1 = Pattern.compile("bytes=(\\d*)-(\\d*)");
        Matcher matcher1 = compile1.matcher(range);
        if (matcher1.find()) {
            String group1 = matcher1.group(1);
            String group2 = matcher1.group(2);
            if (StringUtils.hasLength(group1)) {
                start = Long.parseLong(group1);
            }
            if (StringUtils.hasLength(group2)) {
                end = Long.parseLong(group2);
            }

            if (StringUtils.hasLength(group1) && StringUtils.hasLength(group2)) {
                rangeType = 2;
                httpRange = HttpRange.createByteRange(start, end);
            } else if (StringUtils.hasLength(group1)) {
                rangeType = 1;
                httpRange = HttpRange.createByteRange(start);
            } else {
                response.setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value());
                return;
            }
        } else {
            response.setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value());
            return;
        }

        if (rangeType == 2) {
            getObjectRequest.withRange(start, end);
        } else {
            getObjectRequest.withRange(start);
        }

        S3Object s3Object = amazonS3.getObject(getObjectRequest);
        try {
            response.setHeader(HttpHeaders.CONNECTION, "keep-alive");
            response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
            response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + URLEncoder.encode(fileName, "UTF8").replaceAll("\\+", "%20"));
            response.setContentType(s3Object.getObjectMetadata().getContentType());
            s3ObjectInputStream = s3Object.getObjectContent();
            long contentLength = s3Object.getObjectMetadata().getContentLength();
            String contentRange = s3Object.getObjectMetadata().getRawMetadata().get("Content-Range").toString();

            Pattern compile = Pattern.compile("bytes\\s?(\\d*)-(\\d*)/(\\d*)");
            Matcher matcher = compile.matcher(contentRange);
            long total = 0;
            if (matcher.find()) {
                total = Long.parseLong(matcher.group(3));
            }

            response.setHeader(HttpHeaders.CONTENT_RANGE, createContentRange(httpRange, total));
            response.setContentLengthLong(contentLength);
            response.setStatus(HttpStatus.PARTIAL_CONTENT.value());
            IOUtils.copy(s3ObjectInputStream, outputStream);
            outputStream.close();
            outputStream.flush();
        } catch (Exception e) {
            if (s3ObjectInputStream != null) {
                s3ObjectInputStream.abort();
            }
            IOUtils.closeQuietly(s3Object);
            IOUtils.closeQuietly(outputStream);
            log.warn(e.getMessage());
        }
    }
    public String createContentRange(HttpRange httpRange, long length) {
        return String.format("bytes %d-%d/%d", httpRange.getRangeStart(length), httpRange.getRangeEnd(length), length);
    }

    /*public void getFile(String bucket, String fileName, HttpServletRequest request, HttpServletResponse response) {
        S3ObjectInputStream s3ObjectInputStream = null;
        long skip;
        try (S3Object s3Object = fileTemplate.getObject(bucket, fileName)) {
            response.setHeader(HttpHeaders.CACHE_CONTROL, "max-age=14400");
            response.setContentType("application/octet-stream; charset=UTF-8");
            s3ObjectInputStream = s3Object.getObjectContent();

            String range = request.getHeader(HttpHeaders.RANGE);
            response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20"));

            if (StrUtil.isNotBlank(range)) {
                String[] ranges = range.replaceAll("bytes=", "").split("-");
                skip = Long.parseLong(ranges[0]);
                log.info(String.valueOf(skip));
                IOUtils.skip(s3ObjectInputStream, skip);
                response.setHeader(HttpHeaders.CONTENT_RANGE, String.format("bytes %d-%d/%d", skip, s3Object.getObjectMetadata().getContentLength() - 1, s3Object.getObjectMetadata().getContentLength()));
                response.setContentLengthLong(s3Object.getObjectMetadata().getContentLength() - skip);
                response.setStatus(HttpStatus.PARTIAL_CONTENT.value());
            } else {
                response.setContentLengthLong(s3Object.getObjectMetadata().getContentLength());
            }
            ServletOutputStream outputStream = response.getOutputStream();

            byte[] buffer = new byte[IOUtils.DEFAULT_BUFFER_SIZE];

            int len;

            while (-1 != (len = s3ObjectInputStream.read(buffer))) {
                outputStream.write(buffer, 0, len);
            }
            outputStream.flush();
            s3ObjectInputStream.abort();

            log.debug("文件读取结束");
        } catch (Exception e) {
            log.error("文件读取异常: {}", e.getLocalizedMessage());
        } finally {
            if (s3ObjectInputStream != null) {
                s3ObjectInputStream.abort();
            }
        }
    }*/

    /**
     * 删除文件
     *
     * @param id
     * @return
     */
    @Override
    @SneakyThrows
    @Transactional(rollbackFor = Exception.class)
    public Boolean deleteFile(Long id) {
        SysFile file = this.getById(id);
        fileTemplate.removeObject(properties.getBucketName(), file.getFileName());
        return this.removeById(id);
    }

    /**
     * 文件管理数据记录,收集管理追踪文件
     *
     * @param file     上传文件格式
     * @param fileName 文件名
     */
    private void fileLog(MultipartFile file, String fileName) {
        SysFile sysFile = new SysFile();
        this.saveSysFile(file, sysFile, fileName);
    }

    private SysFile fileLog(MultipartFile file, String fileName, String uniqueCode, String model, String classify) {
        SysFile sysFile = new SysFile();
        sysFile.setUniqueCode(uniqueCode);
        sysFile.setModel(model);
        sysFile.setClassify(classify);
        saveSysFile(file, sysFile, fileName);
        return sysFile;
    }

    private void saveSysFile(MultipartFile file, SysFile sysFile, String fileName) {
        sysFile.setFileName(fileName);
        sysFile.setOriginal(file.getOriginalFilename());
        sysFile.setFileSize(file.getSize());
        sysFile.setType(FileUtil.extName(file.getOriginalFilename()));
        sysFile.setBucketName(properties.getBucketName());
        sysFile.setCreateBy(SecurityUtils.getUser().getName());
        this.save(sysFile);
    }

    /**
     * 上传文件(带参数)
     *
     * @param file
     * @return
     */
    @Override
    public R uploadFile(MultipartFile file, HttpServletRequest request) {
        String fileName = IdUtil.simpleUUID() + StrUtil.DOT + FileUtil.extName(file.getOriginalFilename());
        Map<String, Object> resultMap = new HashMap<>(4);
        resultMap.put("bucketName", properties.getBucketName());
        resultMap.put("fileName", file.getOriginalFilename());
        resultMap.put("url", String.format("/admin/sys-file/%s/%s", properties.getBucketName(), fileName));

        try {
            fileTemplate.putObject(properties.getBucketName(), fileName, file.getInputStream());
            String uniqueCode = request.getParameter("uniqueCode");
            String classify = request.getParameter("classify");
            String model = request.getParameter("model");
            // 文件管理数据记录,收集管理追踪文件
            SysFile sysFile = fileLog(file, fileName, uniqueCode, model, classify);
            resultMap.put("sysFile", JSON.toJSON(sysFile));
        } catch (Exception e) {
            log.error("上传失败", e);
            return R.failed(e.getLocalizedMessage());
        }
        return R.ok(resultMap);
    }

}
